package fr.unice.polytech.chord.core;

import java.io.Serializable;
import java.math.BigInteger;

import org.apache.log4j.Logger;

import fr.unice.polytech.chord.core.util.HashUtil;

/**
 * A {@link Key} represents an identifier used by {@link ChordNode} and
 * {@link Entries} that are stored.
 * 
 * @author Anthony Parisy
 * @author Laurent Pellegrino
 * @author Marc Valdener
 * 
 * @version 10/28/2009
 */
@SuppressWarnings("serial")
public class Key implements Comparable<Key>, Serializable {

	private static final Logger logger = Logger.getLogger(Key.class);

	public static final int NB_BITS = 5;

	private BigInteger value;

	/**
	 * Constructor.
	 * 
	 * @param id
	 *            the identifier to use modulo the size of the key which is
	 *            {@link Key#NB_BITS}.
	 */
	public Key(int id) {
		if (id < 0) {
			throw new NumberFormatException("Number must be positive");
		} else {
			this.value = BigInteger
					.valueOf(id % (int) Math.pow(2, Key.NB_BITS));
		}
	}

	/**
	 * Constructor.
	 * 
	 * @param id
	 *            the identifier to use modulo the size of the key which is
	 *            {@link Key#NB_BITS}.
	 */
	public Key(BigInteger id) {
		this.value = id.mod(BigInteger.valueOf((int) Math.pow(2, Key.NB_BITS)));
	}

	/**
	 * Constructor.
	 * 
	 * @param id
	 *            the identifier to use that will be hashed modulo the size of
	 *            the key which is {@link Key#NB_BITS}.
	 */
	public Key(String id) {
		this.value = HashUtil.getSHA1KeyRestrictedToIntervalByModulo(id,
				(int) Math.pow(2, Key.NB_BITS));
	}

	/**
	 * {@inheritDoc}
	 */
	public int compareTo(Key key) {
		return this.value.compareTo(key.getValue());
	}

	/**
	 * Indicated if the current key is between two others.
	 * 
	 * @param fromKey
	 *            lower bound.
	 * @param toKey
	 *            upper bound.
	 * @return
	 *         <code>true<code> if the current key is between the two others, <code>false</code>
	 *         otherwise.
	 */
	public boolean isBetween(Key fromKey, Key toKey) {
		boolean result;

		if (fromKey.compareTo(toKey) == 0) {
			result = this.compareTo(fromKey) != 0;
		} else if (fromKey.compareTo(toKey) < 0) {
			result = this.compareTo(fromKey) > 0 && this.compareTo(toKey) < 0;
		} else {
			result = this.compareTo(fromKey) > 0 || this.compareTo(toKey) < 0;
		}

		if (logger.isDebugEnabled()) {
			logger.debug(fromKey.getValue() + " < " + this.value + " < "
					+ toKey.getValue() + " ? " + result);
		}

		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object obj) {
		return this.compareTo((Key) obj) == 0;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		return this.value.hashCode();
	}

	/**
	 * Returns the value associated to this key.
	 * 
	 * @return the value associated to this key.
	 */
	public BigInteger getValue() {
		return this.value;
	}

	/**
	 * {@inheritDoc}
	 */
	public String toString() {
		return this.value.toString();
	}
}
